/*
*  Copyright (c) 2006-2020, Chukie
*
*  SPDX-License-Identifier: Apache-2.0
*
*  @file     : dial_sample.c
*
*  @brief    :
*  Website   : https://gitee.com/open-embedded
*
*
*  Change Logs:
*  Date           Author          Notes
*  2021-08-16     zhaocj       The first version
*/
/* Includes -----------------------------------------------------------------*/
#include "dial_sample.h"
#define DBG_TAG "[DIAL]"
#define DBG_LVL DBG_LOG
#include <logtrace.h>
/* Private typedef ----------------------------------------------------------*/
/* Private define -----------------------------------------------------------*/
/* Private variables --------------------------------------------------------*/
struct dial_instance dial =
{
    .device = NULL,
    .state = DIAL_STATE_START_MODEM,
    .redial_wait_time = 0,
#ifdef PKG_NETUTILS_NTP
    .ntp_cfg = {
        .ntp_enable = false,
    },
    .ntp_sync_interval = DIAL_NTP_SYNC_TIMEOUT,
#endif /*PKG_NETUTILS_NTP*/
};
static struct rt_thread thread;
static char stack[DIAL_THREAD_STACK_SIZE];
static uint8_t running = 0;
/* Extern variables ---------------------------------------------------------*/
/* Private function prototypes ----------------------------------------------*/
#define RESET_TIMER(t)  (t)=uptime()
#define IS_TIMEOUT(t,timeout)   time_after(uptime(),(t)+(timeout))

static int cellular_proc_cb(int status, void *args)
{
    if (CELLULAR_OK != status)
    {
        thread_ipc_recv(200);
    }
    else
    {
        thread_ipc_recv(5);
    }
    return (running ? CELLULAR_OK : CELLULAR_ERROR);
}

static void ppp_event_hook(int status, int event)
{
    if (event & CELLULAR_PPP_EVENT_NET_UP)
    {
        LOG_INFO("ppp connected!");
        dial.state = DIAL_STATE_PPP_CONNECTED;
        dial.ppp_connected_ts = uptime();
        //notify all thread ppp is down
        thread_ipc_send(NULL, IPC_EVENT_PPP_UP, NULL);
    }
    else if (event & CELLULAR_PPP_EVENT_NET_DOWN)
    {
        LOG_INFO("ppp disconnected!");
        dial.redial_wait_time = 1;
        dial.state = DIAL_STATE_PPP_DISCONNECTED;
    }
}

#ifdef CELLULAR_USING_RING
static int cellular_ring_callback(cellular_device_t  dev, int ind)
{
    if (CELLULAR_RING_IND_PHONE == ind)
    {
        LOG_DBG("Rcv a phone call!");
    }
    else if (CELLULAR_RING_IND_SMS == ind)
    {
        LOG_DBG("Rcv a new sms!");
    }
    return RT_EOK;
}
#endif /*CELLULAR_USING_RING*/

static void dial_display_config(dial_instance_t instance, bool consol)
{
    dial_config_t dial_cfg = NULL;
    PRINT_INFO(consol, "Cellular device name: %s", CELLULAR_SAMPLE_USING_CELLULAR_NAME);
    if (!instance->device)
    {
        LOG_WARN("Can't find cellular %s", CELLULAR_SAMPLE_USING_CELLULAR_NAME);
        return;
    }
    dial_cfg = &(instance->device->dial_info.config[0]);
    PRINT_INFO(consol, "Dial config: ");
    PRINT_INFO(consol, "    SIM1 dial number         : %s", dial_cfg->ppp_cfg.dial_number);
    PRINT_INFO(consol, "    SIM1 APN                 : %s", dial_cfg->apn);
    PRINT_INFO(consol, "    SIM1 username            : %s", dial_cfg->ppp_cfg.user_name);
    PRINT_INFO(consol, "    SIM1 password            : %s", dial_cfg->ppp_cfg.password);
    PRINT_INFO(consol, "    SIM1 ppp auth type       : %s", cellular_ppp_auth2str(dial_cfg->ppp_cfg.auth));
    PRINT_INFO(consol, "    SIM1 network mode        : %s", cellular_network_mode2str(dial_cfg->network_mode));

    PRINT_INFO(consol, "\n");

#ifdef PKG_NETUTILS_NTP
    PRINT_INFO(consol, "NTP config: ");
    PRINT_INFO(consol, "    NTP client enable        : %s", BOOL_2_STR(instance->ntp_cfg.ntp_enable));
    PRINT_INFO(consol, "    NTP server               : %s", instance->ntp_cfg.ntp_server);
    PRINT_INFO(consol, "\n");
#endif /*PKG_NETUTILS_NTP*/

}

static int dial_load_config(dial_instance_t instance)
{
    dial_config_t dial_cfg = NULL;
    uint8_t uint8_val;
    uint16_t uint16_val;
    uint32_t uint32_val = 0;
    char str[12] = {0};

    /* dial apn/user name/password.....*/
    dial_cfg = &(instance->device->dial_info.config[0]);
    memset(dial_cfg, 0, sizeof(struct dial_config));
    snprintf(dial_cfg->ppp_cfg.dial_number, sizeof(dial_cfg->ppp_cfg.dial_number), DEFAULT_DIAL_NUMBER);
    snprintf(dial_cfg->apn, sizeof(dial_cfg->apn), DEFAULT_DIAL_APN);
    rt_snprintf(dial_cfg->ppp_cfg.user_name, sizeof(dial_cfg->ppp_cfg.user_name), DEFAULT_DIAL_USERNAME);
    rt_snprintf(dial_cfg->ppp_cfg.password, sizeof(dial_cfg->ppp_cfg.password), DEFAULT_DIAL_PASSWORD);
    dial_cfg->ppp_cfg.auth = DEFAULT_DIAL_AUTH_TYPE;
    dial_cfg->network_mode = DEFAULT_DIAL_NETWORK_MODE;

#ifdef PKG_NETUTILS_NTP
    instance->ntp_cfg.ntp_enable = true;
    snprintf(instance->ntp_cfg.ntp_server, sizeof(instance->ntp_cfg.ntp_server), "%s", "");
#endif /*PKG_NETUTILS_NTP*/

    dial_display_config(instance, false);
    return 1;
}

static int dial_loop(dial_instance_t instance)
{
    int ret = -RT_ERROR;

#ifdef PKG_NETUTILS_NTP
    if (instance->ntp_cfg.ntp_enable && time_after(uptime(), instance->ntp_sync_ts + instance->ntp_sync_interval))
    {
        LOG_INFO("Begain to sync ntp time...");
        instance->ntp_sync_ts = uptime();
        time_t t = ntp_sync_to_rtc(strlen(instance->ntp_cfg.ntp_server) ? instance->ntp_cfg.ntp_server : NULL);
        if (t)
        {
            LOG_INFO("The system time is updated to %s", ctime((const time_t *) &t));
            instance->ntp_sync_interval = DIAL_NTP_SYNC_INTERVAL;
        }
        else
        {
            instance->ntp_sync_interval = DIAL_NTP_SYNC_TIMEOUT;
            LOG_W("NTP sync failed!");
        }
        LOG_INFO("NTP sync next time after %ds", instance->ntp_sync_interval);
    }
#endif /*PKG_NETUTILS_NTP*/
    return ret;
}

static void dial_thread_entry(void *param)
{
#define ERR_NEED_CLOSE_DEV      1
#define ERR_NEED_SWITCH_RF      2
#define ERR_NEED_REBOOT         3
#define ERR_NEED_RESET          4
    uint8_t err_cnt = 0;
    uint8_t sim_err_cnt = 0;
    uint8_t network_err_cnt = 0;
    uint8_t ppp_err_cnt = 0;
    uint8_t redial_times = 0;
    uint8_t err_opt = 0;
    int ret = 0;
    dial_state_t switch_to_state = DIAL_STATE_START_MODEM;
    dial_instance_t instance = (dial_instance_t)param;
    RT_ASSERT(instance);
    LOG_INFO("Start..");
    running = 1;
    while (running)
    {
        if (ret == 0)
        {
            if (strlen(CELLULAR_SAMPLE_USING_CELLULAR_NAME))
            {
                instance->device = cellular_find_attach(CELLULAR_SAMPLE_USING_CELLULAR_NAME, cellular_proc_cb, &dial);
                if (instance->device)
                {
#ifdef CELLULAR_USING_RING
                    cellular_set_ring_cb(instance->device, cellular_ring_callback);
#endif /*CELLULAR_USING_RING*/
                    break;
                }
                else
                {
                    LOG_ERR("Can't find cellular device named %s, try again after 60s...", CELLULAR_SAMPLE_USING_CELLULAR_NAME);
                }
            }
            else
            {
                LOG_ERR("找不到模组, 设备需要定型!");
            }
        }
        if (ret++ > 6000)
        {
            ret = 0;
        }

        thread_ipc_recv(10);
    }
    dial_load_config(instance);

    instance->state = DIAL_STATE_START_MODEM;
    instance->timer = uptime();
    err_cnt = 0;
    while (running)
    {
        thread_ipc_recv(5);
        //LOG_DBG("instance->state: %d",instance->state);

        switch (instance->state)
        {
        case DIAL_STATE_START_MODEM:
        {
            ret = cellular_module_power(instance->device,
                                        CELLULAR_PORT_TYPE_PPP, DIAL_WAIT_MODULE_START_TIMEOUT,
                                        (void *)1);
            if (CELLULAR_OK == ret)
            {
                instance->state = DIAL_STATE_INIT_MODEM;
                err_cnt = 0;
            }
            else if (CELLULAR_INPROG != ret)
            {

                instance->redial_wait_time = 0;
                instance->state = DIAL_STATE_FAILED;
                if (err_cnt++ > DIAL_MAX_ERR_TIMES)
                {
                    err_opt = ERR_NEED_REBOOT;
                }
                else
                {
                    err_opt = ERR_NEED_RESET;
                }
            }
            break;
        }
        case DIAL_STATE_INIT_MODEM:
        {
            ret = cellular_module_init(instance->device, CELLULAR_PORT_TYPE_PPP,
                                       DIAL_WAIT_MODULE_INIT_TIMEOUT, NULL);
            if (CELLULAR_OK == ret)
            {
                instance->state = DIAL_STATE_CHECK_SIM;
                err_cnt = 0;
            }
            else if (CELLULAR_INPROG != ret)
            {
                instance->redial_wait_time = 0;
                instance->state = DIAL_STATE_FAILED;
                if (err_cnt++ > DIAL_MAX_ERR_TIMES)
                {
                    err_opt = ERR_NEED_REBOOT;
                }
                else
                {
                    err_opt = ERR_NEED_CLOSE_DEV;
                }
            }
            break;
        }
        case DIAL_STATE_CHECK_SIM:
        {
            ret = cellular_check_sim(instance->device, CELLULAR_PORT_TYPE_PPP,
                                     DIAL_WAIT_CHECK_SIM_TIMEOUT, NULL);
            if (CELLULAR_OK == ret)
            {
                instance->state = DIAL_STATE_CONFIG;
                sim_err_cnt = 0;
                err_cnt = 0;
            }
            else if (CELLULAR_INPROG != ret)
            {
                if (sim_err_cnt++ >= DIAL_MAX_SIM_ERROR_TIMES)
                {
                    LOG_ERR("Check SIM failed for %d times, wait for %ds...", DIAL_MAX_SIM_ERROR_TIMES, DIAL_WAIT_TIME_WHILE_SIM_ERR);
                    instance->redial_wait_time = DIAL_WAIT_TIME_WHILE_SIM_ERR;
                    instance->state = DIAL_STATE_FAILED;
                    sim_err_cnt = 0;
                    if (err_cnt++ > DIAL_MAX_ERR_TIMES)
                    {
                        err_opt = ERR_NEED_REBOOT;
                    }
                    else
                    {
                        err_opt = ERR_NEED_CLOSE_DEV;
                    }
                    break;
                }
            }
            break;
        }
        case DIAL_STATE_CONFIG:
        {
            ret = cellular_module_setup(instance->device, CELLULAR_PORT_TYPE_PPP,
                                        DIAL_WAIT_MODULE_CONFIG_TIMEOUT, NULL);
            if (CELLULAR_OK == ret)
            {
                err_cnt = 0;
                instance->state = DIAL_STATE_CHECK_NETWORK;
            }
            else if (CELLULAR_INPROG != ret)
            {
                instance->state = DIAL_STATE_FAILED;
                instance->redial_wait_time = 0;
                if (err_cnt++ > DIAL_MAX_ERR_TIMES)
                {
                    err_opt = ERR_NEED_REBOOT;
                }
                else
                {
                    err_opt = ERR_NEED_CLOSE_DEV;
                }
            }
            break;
        }
        case DIAL_STATE_CHECK_NETWORK:
        {
            ret = cellular_check_network(instance->device, CELLULAR_PORT_TYPE_PPP,
                                         DIAL_WAIT_CHECK_NETWORK_TIMEOUT, NULL);
            if (CELLULAR_OK == ret)
            {
                network_err_cnt = 0;
                instance->state = DIAL_STATE_PPP_START;
            }
            else if (CELLULAR_INPROG != ret)
            {
                if (network_err_cnt++ < DIAL_MAX_ERR_TIMES)
                {
                    LOG_ERR("Check network failed!");
                    instance->redial_wait_time = 0;
                    instance->state = DIAL_STATE_FAILED;
                    err_opt = ERR_NEED_SWITCH_RF;
                }
                else
                {
                    LOG_ERR("Check network failed times reach %d, need reboot!", DIAL_MAX_ERR_TIMES);
                    instance->redial_wait_time = 0;
                    instance->state = DIAL_STATE_FAILED;
                    err_opt = ERR_NEED_REBOOT;
                }
            }
            break;
        }
        case DIAL_STATE_PPP_START:
        {
            if (cellular_ppp_start(instance->device, DEFAULT_PPP_ECHO_INTERVAL, ppp_event_hook) == CELLULAR_OK)
            {
                instance->state = DIAL_STATE_PPP_CONNECTING;
            }
            else
            {
                instance->state = DIAL_STATE_PPP_DISCONNECTED;
            }
            break;
        }
        case DIAL_STATE_PPP_CONNECTING:
        {
            if (IS_TIMEOUT(instance->timer, DIAL_PPP_TIMEOUT))
            {
                instance->redial_wait_time = 0;
                instance->state = DIAL_STATE_PPP_DISCONNECTED;
                LOG_WARN("PPP connect timeout!");
                /*拨号失败则切卡或者重播*/

                RESET_TIMER(instance->timer);
            }
            break;
        }
        case DIAL_STATE_PPP_CONNECTED:
        {
            redial_times = 0;
            dial_loop(instance);
            break;
        }
        case DIAL_STATE_PPP_DISCONNECTED:
        {
            if (cellular_ppp_get_state(instance->device))
            {
                LOG_INFO("Broadcast ppp down notification...");
                /*广播下线消息 ,以便于使用网络的线程及时关闭socket*/
                thread_ipc_send(NULL, IPC_EVENT_PPP_DOWN, NULL);
                sleep(1);
            }

            ret = cellular_ppp_stop(instance->device);
            if (CELLULAR_OK != ret)
            {
                err_opt = ERR_NEED_CLOSE_DEV;
            }
            else
            {
                if (ppp_err_cnt++ >= DIAL_MAX_ERR_TIMES)
                {
                    LOG_WARN("PPP disconnect for %d times, need to close cellular!", ppp_err_cnt);
                    err_opt = ERR_NEED_CLOSE_DEV;
                    instance->redial_wait_time = 0;
                    redial_times++;
                }
                else if (0 == (ppp_err_cnt % 2))
                {
                    LOG_WARN("PPP disconnect for %d times, need to switch cellular RF!", ppp_err_cnt);
                    err_opt = ERR_NEED_SWITCH_RF;
                    instance->redial_wait_time = 10;
                }
            }
            instance->state = DIAL_STATE_FAILED;
            break;
        }
        case DIAL_STATE_FAILED:
        {
            cellular_ppp_stop(instance->device);
            switch_to_state = DIAL_STATE_CHECK_SIM;
            if (err_opt == ERR_NEED_CLOSE_DEV)
            {
                cellular_module_power(instance->device, CELLULAR_PORT_TYPE_PPP, \
                                      30, NULL);
                switch_to_state = DIAL_STATE_START_MODEM;
                instance->redial_wait_time = 10;
            }
            else if (err_opt == ERR_NEED_SWITCH_RF)
            {
                switch_to_state = DIAL_STATE_CHECK_SIM;
                if (CELLULAR_OK != cellular_switch_rf(instance->device, CELLULAR_PORT_TYPE_PPP, \
                                                      10, NULL))
                {
                    cellular_module_reset(instance->device, CELLULAR_PORT_TYPE_PPP, \
                                          30, NULL);
                    switch_to_state = DIAL_STATE_START_MODEM;
                }

            }
            else if (err_opt == ERR_NEED_RESET)
            {
                switch_to_state = DIAL_STATE_START_MODEM;
                instance->redial_wait_time = 10;
                cellular_module_reset(instance->device, CELLULAR_PORT_TYPE_PPP, \
                                      30, NULL);
            }

            if (0 == instance->redial_wait_time)
            {
                instance->redial_wait_time = DEFAULT_REDIAL_INTERVAL;
            }
            instance->state = DIAL_STATE_WAITTING;

            if (redial_times++  >= DEFAULT_REDIAL_MAX_TIMES || ERR_NEED_REBOOT == err_opt)
            {
                LOG_WARN("Redial to many times, reboot...");
                system_reboot();
            }
            else
            {
                LOG_INFO("Redial %d|%d(redial times|max times) after %ds",
                         redial_times, DEFAULT_REDIAL_MAX_TIMES, instance->redial_wait_time);
            }
            err_opt = 0;
            RESET_TIMER(instance->timer);
            break;
        }
        case DIAL_STATE_WAITTING:
        {
            if (IS_TIMEOUT(instance->timer, instance->redial_wait_time))
            {
                instance->redial_wait_time = 0;
                instance->state = switch_to_state;
                LOG_DBG("Switch to state %d", switch_to_state);
                RESET_TIMER(instance->timer);
            }
        }
        default:
            break;
        }

    }

    if (instance->device)
    {
        cellular_ppp_stop(instance->device);
        cellular_module_power(instance->device, CELLULAR_PORT_TYPE_PPP, 30, NULL);
    }
#if defined(SYSTEM_USING_SYSWATCHER)
    tm_delete_self();
#endif
}

#ifdef SYSTEM_USING_SYSWATCHER

void dial_ipc_handler(rt_uint32_t ev, void *msg, void *thread_param)
{
    if (ev & IPC_EVENT_SYS_KILL)
    {
        LOG_W("Recv kill event, exit...");
        running = 0;
    }
    else if (ev & IPC_EVENT_REDIAL)
    {
        LOG_W("Some one request to redial...");
        if (dial.state > DIAL_STATE_START_MODEM)
        {
            dial.redial_wait_time = 1;
            dial.state = DIAL_STATE_PPP_DISCONNECTED;
        }
    }
    else if (IPC_EVENT_DATA_ACTIVE & ev)
    {

    }
    else if (IPC_EVENT_PHONE_ACTIVE & ev)
    {
        if (dial.device)
        {
            cellular_hangup_call(dial.device, CELLULAR_PORT_TYPE_PPP, 10, NULL);
        }
        LOG_I("Rcv Phone call!");
    }
    else if (IPC_EVENT_SMS_ACTIVE & ev)
    {

    }
    else if (IPC_EVENT_GW_ERROR & ev)
    {
        LOG_D("Recv gateway error event!");

        dial.state = DIAL_STATE_PPP_DISCONNECTED;
    }
    else if (IPC_EVENT_SWITCH_SIM & ev)
    {
        LOG_I("Recv switch sim request event!");

    }
    else if (IPC_EVENT_QUERY_MODEM & ev)
    {
        if (dial.state >= DIAL_STATE_CHECK_SIM)
        {
            cellular_check_sim(dial.device, CELLULAR_PORT_TYPE_AT, 10, &running);
            cellular_check_network(dial.device, CELLULAR_PORT_TYPE_AT, 10, &running);
        }
    }
}

void dial_cleanup(void *args)
{
    if (dial.device)
    {
        cellular_ppp_stop(dial.device);
        cellular_module_power(dial.device, CELLULAR_PORT_TYPE_PPP, 30, NULL);
    }
}
#endif /*SYSTEM_USING_SYSWATCHER*/

static int dial_init(void)
{
    rt_thread_init(&thread,
                   DIAL_THREAD_NAME,
                   dial_thread_entry,
                   &dial,
                   &stack[0],
                   DIAL_THREAD_STACK_SIZE,
                   DIAL_THREAD_PRIORITY, DIAL_THREAD_TIMESLICE);
#ifdef SYSTEM_USING_SYSWATCHER
    uint32_t listen_event = IPC_EVENT_SYS_KILL;
    listen_event |= IPC_EVENT_REDIAL;
    listen_event |= IPC_EVENT_DATA_ACTIVE;
    listen_event |= IPC_EVENT_PHONE_ACTIVE;
    listen_event |= IPC_EVENT_SMS_ACTIVE;
    listen_event |= IPC_EVENT_SWITCH_SIM;
    listen_event |= IPC_EVENT_QUERY_MODEM;
    listen_event |= IPC_EVENT_GW_ERROR;
    tm_thread_add(&thread, DIAL_THREAD_NAME, 60000, dial_cleanup, listen_event, dial_ipc_handler);
#else
    rt_thread_startup(&thread);
#endif /*SYSTEM_USING_SYSWATCHER*/

    return RT_EOK;
}
INIT_APP_EXPORT(dial_init);
/* Public function prototypes -----------------------------------------------*/


/* End of file****************************************************************/

